package Reflection;

import org.junit.Test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/*通过类对象获取到成员变量 Class类对象的方法
                - getField(变量名) 返回Field类型的成员变量，仅限于public修饰的全局变量

                - getFields() 返回Field数组，获取到源文件所有public修饰的全局变量

                    通过上两个方法，获取到了Field类型的对象，其实针对源文件中的全局变量的抽象类，代表了所有的全局变量
                    Field 还提供了一些方法
                       - getName() 获取到源文件的全局变量名
                       - get(Object) 参数是源文件对象，从而获取到源文件的全局变量的值
                       - set(Object,"") 参数1：源文件对象；参数2：变量值
                       - getType() 获取到全局变量的类型

                        Person p = new Person()
                        p.getName()
                        成员变量.get(对象)
                        类对象提供的一些方法：
                - getConstructor() 返回Constructor对象,获取到源文件的无参构造器
                                   参数列表:可变参数,可以给他指定参数类型,从而调用有参的构造函数
                - getConstructors() 返回Constructor数组,获取到源文件中,所有public修饰的构造器

                - getDeclaredConstructor() 返回Constructor对象,如果没有参数,指定调用无参的构造器
                                           如果指定参数,就会调用相应的有参构造函数
                    注意:私有化的构造器,可以通过  getDeclaredConstructor() 反射得到,但是呢,不能对其进行操作
                         想要操作私有的构造器,就需要设置暴力反射

                - getDeclaredConstructors() 返回Constructor数组,获取所有的构造器

                    获取到了Constructor对象管理源文件的构造器，提供了一些方法：
                        - newInstance() 通过调用无参的构造函数，从而创建对象
                                        根据Constructor中的参数列表,给对象赋值
                        - setAccessible(boolean) 开启暴力反射

                        注意:
                            1.类对象中也有一个  newInstance()，其实就是对通过Constructor对象创建的简化
                            2.一定要给源文件添加无参构造器,否则会在调用无参构造方法时报错
         类对象操纵成员方法,提供的一些方法:
                - getMethod(String,Object...) 返回Method对象,通过指定方法名的方式,获取到源文件中的成员方法
                                              参数1:方法名;参数2:方法的参数列表的类型

                - getMethods() 返回Method数组,获取到源文件中所有的 public 修饰的方法,包含了父类中的公有方法

                - getDeclaredMethod(String,Object...) 返回Method对象,访问私有化的方法

                - getDeclaredMethods() 返回Method数组,获取到源文件中声明的所有方法

                Method对象又提供了一些方法:
                    - invoke(Object,...agrs) 执行源文件中的方法
                                             参数1:对象;参数2:方法的实参
                        Person p = new Person()
                        p.eat()

                        method.invoke(p)

                    - getName() 获取到方法名

                    - getParameterTypes() 得到方法中的所有参数类型,返回的Type数组

                    - setAccessible(boolean) 暴力反射*/
public class Demo1 {
    @Test//反射之前对于Person类的操作
    public void test1(){
        //创建对象，调属性和方法
        Person p = new Person(1,"顾新宇");
        System.out.println(p.name);
        System.out.println(p.getAge());
        p.show();
        //外部类创建对象不能调私有的构造器、属性、方法
    }

    @Test//反射之后，对于Person类的操作
    public void test2() throws Exception{
        Class clazz = Class.forName("Reflection.Person");
        //反射之后，创建Person类对象
        Person p = (Person)clazz.newInstance();//这个方法底层就是通过无参构造器newInstance
        //==通过无参构造创建对象
//        Constructor constructor = clazz.getConstructor();
//        Object o1 = constructor.newInstance();
//        Person p1 = (Person) o1;

        //通过有参构造创建对象
//        Constructor cons = clazz.getConstructor(int.class, String.class);
//        Object obj = cons.newInstance(22, "心动新新呀");
//        Person p = (Person) obj;
//        System.out.println(p);

        //反射之后调用对象的公共属性和方法
        Field name = clazz.getField("name");
        name.set(p,"顾新宇");
        Object o = name.get(p);
        System.out.println(o);

        Method show = clazz.getMethod("show");
        show.invoke(p);

        //获取当前类的所有成员变量包括公有和私有
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            System.out.println(field.getType());//变量类型 int String
            System.out.println(field.getName());//变量名age name
        }


        //反射创建的对象可以调私有的构造器、属性和方法
        Constructor cons1 = clazz.getDeclaredConstructor(int.class);
        cons1.setAccessible(true);//强制获取到私有的属性和方法
        Person p1  =(Person) cons1.newInstance(11);
        System.out.println(p1);

        Field age1 = clazz.getDeclaredField("age");
        age1.setAccessible(true);
        age1.set(p1,20);
        System.out.println(p1);

        Method showNation = clazz.getDeclaredMethod("showNation", String.class);
        showNation.setAccessible(true);
        showNation.invoke(p1,"中国");//输出
        //获取方法的返回值
        String nation = (String) showNation.invoke(p1,"中国");
        System.out.println(nation);
    }

    @Test
    /*1.类的加载过程：
    *     程序经过javac.exe命令以后，会生成字节码文件（.class结尾）。
    *     接着我们使用java.exe命令对某个字节码文件进行解释运行，相当于将某个
    *     字节码文件加载到内存中，此过程就称为类的加载。加载到内存中的类，我们
    *     就称为运行时类，此运行时类，就作为Class的一个实例。
    *
    * 2.换句话说，Class的实例就对应着一个运行时类。
    * 3.加载到内存中的运行时类，会缓存一段时间，在此时间之内，我们可以通过不同
    *     的方式来获取运行时类。
    * */

    public void test3() throws Exception{
        //获取Class的实例的方式(三种，重点掌握第三种)这三种方式创建的实例都是一样的

        //方式一：调用运行时类的类名 .class
        Class c1 = Person.class;
        System.out.println(c1);
        //方式二：通过运行时类的对象.getClass()
        Person p  = new Person();
        Class c2 = p.getClass();
        System.out.println(c2);
        //方式三：调用Class的静态方法：forName(类路径)
        Class c3 = Class.forName("Reflection.Person");
        System.out.println(c3);
    }

}
class Person{
    private int age;
    public String name;

    public Person() {
    }

    public Person(int age, String name) {
        this.age = age;
        this.name = name;
    }
    private Person(int age){
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void show(){
        System.out.println("你好");
    }

    private String showNation(String nation){
        System.out.println("国籍是："+nation);
        return nation;
    }
}